跳到主要内容

33 QSPI 外部flash读写实验

在这里插入图片描述

开发板官网https://www.edevkit.com/)

本系列教程目录:


QSPI 外部 Flash 读写实验

本章目标:

  • 了解 RA6M5 处理器的 QSPI 模块
  • 学会使用 RASC 配置 QSPI 模块,使用 API 读写数据。

硬件设计(略,参见配套硬件原理图)

软件设计

新建工程

复制一份 "07 SCI_UART_Recv_send" 修改文件夹名 "33 QSPI_flash",双击打开 EBF_RA6M5.uvprojx 工程,通过 Keil 的新建文件新建 qspi.cqspi.h 保存到目录下的 \\src 文件夹下。

FSP 配置

Keil 界面选择 “Tools”→“RA Smart Configurator” 打开 RASC 界面。

在 “Pins” 添加 QSPI 设备。

插图01

添加 QSPI Stack 模块。

插图02

配置 QSPI Stack 参数。

插图03

配置完成之后可以按下快捷键"Ctrl + S"保存,最后点击 "Generate Project Content" 按钮,让软件自动生成配置代码即可。

程序设计

实现 QSPI 的读写函数,根据数据长度自动换页,写入一组字符串,一组 float 数据,一组 int 数据,分别读取出来,打印到串口查看数据是否正确。

  1. 打开 QSPI 设备
fsp_err_t R_QSPI_Open(spi_flash_ctrl_t * p_ctrl,
spi_flash_cfg_t const * const p_cfg);
  1. 擦除扇区
fsp_err_t R_QSPI_Erase(spi_flash_ctrl_t * p_ctrl,
uint8_t * const p_device_address,
uint32_t byte_count);
  1. 直接写指令数据
fsp_err_t R_QSPI_DirectWrite(spi_flash_ctrl_t * p_ctrl,
uint8_t const * const p_src,
uint32_t const bytes,
bool const read_after_write);
  1. 直接读指令数据
fsp_err_t R_QSPI_DirectRead(spi_flash_ctrl_t * p_ctrl,
uint8_t * const p_dest,
uint32_t const bytes);
  1. 写入页数据
fsp_err_t R_QSPI_Write(spi_flash_ctrl_t * p_ctrl,
uint8_t const * const p_src,
uint8_t * const p_device_address,
uint32_t byte_count);
  1. 读取页数据
fsp_err_t R_QSPI_Read(spi_flash_ctrl_t * p_ctrl,
uint8_t * const p_device_address,
uint8_t * const p_dest,
uint32_t byte_count);
  1. 状态读取
fsp_err_t R_QSPI_StatusGet(spi_flash_ctrl_t * p_ctrl,
spi_flash_status_t * const p_status);

程序编写

qspi.h 头文件定义相关函数和宏定义。

#ifndef __BSP_QSPI_FLASH_H
#define __BSP_QSPI_FLASH_H
#include "hal_data.h"

#define W25X16 0xEF3015
#define W25Q16 0xEF4015
#define W25Q128 0XEF4018
#define W25Q64 0XEF4017
#define W25Q32 0xEF4016
#define AT25SF321B 0x1F8701
#define sFLASH_ID W25Q32

#define FLASH_ID_W25Q32JV 0xEF4016 // W25Q32JV
#define FLASH_ID_AT25SF321B 0x1F8701 // AT25SF321B

#define SPI_FLASH_PageSize 256
#define SPI_FLASH_PerWritePageSize 256
#define RESET_VALUE (0x00)


/*命令定义-开头*******************************/
#define WriteEnable 0x06
#define WriteDisable 0x04
#define ReadStatusReg 0x05
#define WriteStatusReg 0x01
#define ReadData 0x03
#define FastReadData 0x0B
#define FastReadDual 0x3B
#define PageProgram 0x02
#define BlockErase 0xD8
#define SectorErase 0x20
#define ChipErase 0xC7
#define PowerDown 0xB9
#define ReleasePowerDown 0xAB
#define DeviceID 0xAB
#define ManufactDeviceID 0x90
#define JedecDeviceID 0x9F

/* WIP(busy)标志,FLASH内部正在写入 */
#define WIP_Flag 0x01
#define Dummy_Byte 0xFF
/*命令定义-结尾*******************************/



void QSPI_Flash_Init(void);
uint32_t QSPI_Flash_ReadID(void);
uint32_t QSPI_Flash_ReadDeviceID(void);
void QSPI_Flash_PowerDown(void);
void QSPI_Flash_WakeUp(void);
void QSPI_Flash_WriteEnable(void);
fsp_err_t QSPI_Flash_WaitForWriteEnd(void);

void QSPI_Flash_SectorErase(uint32_t adress);
void QSPI_Flash_PageWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite); //页写入
void QSPI_Flash_BufferWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void QSPI_Flash_BufferRead(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);


//static void qspi_d0_byte_write_standard (uint8_t byte);
fsp_err_t R_QSPI_Read(spi_flash_ctrl_t *p_ctrl, uint8_t *p_src, uint8_t *const p_dest, uint32_t byte_count);

#endif

qspi.c 中实现 ID 读取,设备 ID 读取,用于判断 Flash 是否存在。

#include "qspi.h"
#include "stdio.h"

void QSPI_Flash_Init(void)
{
R_QSPI_Open(&g_qspi_flash_ctrl, &g_qspi_flash_cfg);
R_BSP_SoftwareDelay(10u, BSP_DELAY_UNITS_MILLISECONDS);
}


/**
* @brief 读取FLASH ID
* @param 无
* @retval FLASH ID
*/
uint32_t QSPI_Flash_ReadID(void)
{
unsigned char data[6] = {};
uint32_t back;
data[0] = JedecDeviceID;

R_QSPI_DirectWrite(&g_qspi_flash_ctrl, &data[0], 1, true);
R_QSPI_DirectRead(&g_qspi_flash_ctrl, &data[0], 3);

back = ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | ((uint32_t)data[2]);

return back;
}


/**
* @brief 读取FLASH 设备ID
* @param 无
* @retval FLASH ID
*/
uint32_t QSPI_Flash_ReadDeviceID(void)
{
unsigned char data[6] = {};
uint32_t back;
data[0] = DeviceID;
data[1] = 0xff;
data[2] = 0xff;
data[3] = 0xff;
R_QSPI_DirectWrite(&g_qspi_flash_ctrl, &data[0], 4, true);
R_QSPI_DirectRead(&g_qspi_flash_ctrl, &data[0], 3);

back = ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | ((uint32_t)data[2]);

return back;
}


/**
* @brief 进入掉电模式
* @param 无
*/
void QSPI_Flash_PowerDown(void)
{
unsigned char data[6] = {};
data[0] = PowerDown;
R_QSPI_DirectWrite(&g_qspi_flash_ctrl, &data[0], 1, false);
}

/**
* @brief 唤醒
* @param 无
*/
void QSPI_Flash_WakeUp(void)
{
unsigned char data[6] = {};
data[0] = ReleasePowerDown;
R_QSPI_DirectWrite(&g_qspi_flash_ctrl, &data[0], 1, false);
}


/**
* @brief 向FLASH发送 写使能 命令
* @param none
* @retval none
*/
void QSPI_Flash_WriteEnable(void)
{
unsigned char data[6] = {};
data[0] = WriteEnable;
R_QSPI_DirectWrite(&g_qspi_flash_ctrl, &data[0], 1, false);
}

等待写入数据完成。

/**
* @brief 等待WIP(BUSY)标志被置0,即等待FLASH内部数据写入完毕
* @param 无
*/
fsp_err_t QSPI_Flash_WaitForWriteEnd(void)
{
spi_flash_status_t status = {.write_in_progress = true};
int32_t time_out = (INT32_MAX);
fsp_err_t err = FSP_SUCCESS;

do
{
/* Get status from QSPI flash device */
err = R_QSPI_StatusGet(&g_qspi_flash_ctrl, &status);
if (FSP_SUCCESS != err)
{
printf("R_QSPI_StatusGet Failed\r\n");
return err;
}

/* Decrement time out to avoid infinite loop in case of consistent failure */
--time_out;

if (RESET_VALUE >= time_out)
{
printf("\r\n ** Timeout : No result from QSPI flash status register ** \r\n");
return FSP_ERR_TIMEOUT;
}

}
while (false != status.write_in_progress);

return err;
}

擦除扇区函数,根据写入数据长度自动换页函数,读取函数。

/**
* @brief 擦除FLASH扇区
* @param SectorAddr:要擦除的扇区地址
* @retval 无
*/
void QSPI_Flash_SectorErase(uint32_t adress)
{
unsigned char data[6] = {};

data[0] = 0x06; //write_enable_command
data[1] = 0x20; //erase_command
data[2] = (uint8_t)(adress >> 16);
data[3] = (uint8_t)(adress >> 8);
data[4] = (uint8_t)(adress);
R_QSPI->SFMCMD = 1U;
R_QSPI->SFMCOM = data[0];
R_QSPI_DirectWrite(&g_qspi_flash_ctrl, &data[1], 4, false);

QSPI_Flash_WaitForWriteEnd();
}


/**
* @brief 对FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区
* @param pBuffer,要写入数据的指针
* @param WriteAddr,写入地址
* @param NumByteToWrite,写入数据长度,必须小于等于页大小
* @retval 无
*/
void QSPI_Flash_PageWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, NumByteToWrite);
QSPI_Flash_WaitForWriteEnd();
}


/**
* @brief 对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
* @param pBuffer,要写入数据的指针
* @param WriteAddr,写入地址
* @param NumByteToWrite,写入数据长度
* @retval 无
*/
void QSPI_Flash_BufferWrite(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;

//mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0
Addr = WriteAddr % SPI_FLASH_PageSize;

//差count个数据值,刚好可以对齐到页地址
count = SPI_FLASH_PageSize - Addr;
//计算出要写多少整数页
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
//mod运算求余,计算出剩余不满一页的字节数
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

//Addr=0,则WriteAddr 刚好按页对齐 aligned
if (Addr == 0)
{
//NumByteToWrite < SPI_FLASH_PageSize
if (NumOfPage == 0)
{
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, NumByteToWrite);
QSPI_Flash_WaitForWriteEnd();

}
else //NumByteToWrite > SPI_FLASH_PageSize
{
//先把整数页都写了
while (NumOfPage--)
{
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, SPI_FLASH_PageSize);
QSPI_Flash_WaitForWriteEnd();

WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
//若有多余的不满一页的数据,把它写完
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, NumOfSingle);
QSPI_Flash_WaitForWriteEnd();

}
}
//若地址与 SPI_FLASH_PageSize 不对齐
else
{
//NumByteToWrite < SPI_FLASH_PageSize
if (NumOfPage == 0)
{
//当前页剩余的count个位置比NumOfSingle小,一页写不完
if (NumOfSingle > count)
{
temp = NumOfSingle - count;
//先写满当前页
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, count);
QSPI_Flash_WaitForWriteEnd();


WriteAddr += count;
pBuffer += count;
//再写剩余的数据
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, temp);
QSPI_Flash_WaitForWriteEnd();

}
else //当前页剩余的count个位置能写完NumOfSingle个数据
{
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, NumByteToWrite);
QSPI_Flash_WaitForWriteEnd();

}
}
else // NumByteToWrite > SPI_FLASH_PageSize
{
//地址不对齐多出的count分开处理,不加入这个运算
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

// 先写完count个数据,为的是让下一次要写的地址对齐
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, count);
QSPI_Flash_WaitForWriteEnd();

// 接下来就重复地址对齐的情况
WriteAddr += count;
pBuffer += count;
//把整数页都写
while (NumOfPage--)
{
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, SPI_FLASH_PageSize);
QSPI_Flash_WaitForWriteEnd();

WriteAddr += SPI_FLASH_PageSize;
pBuffer += SPI_FLASH_PageSize;
}
//若有多余的不满一页的数据,把它写完
if (NumOfSingle != 0)
{
R_QSPI_Write(&g_qspi_flash_ctrl, pBuffer, WriteAddr, NumOfSingle);
QSPI_Flash_WaitForWriteEnd();
}
}
}
}


/**
* @brief 读取FLASH数据,减少ctrl这个标志
* @param pBuffer,存储读出数据的指针
* @param ReadAddr,读取地址
* @param NumByteToRead,读取数据长度
* @retval 无
*/
void QSPI_Flash_BufferRead(uint8_t *pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
R_QSPI_Read(&g_qspi_flash_ctrl, pBuffer, ReadAddr, NumByteToRead);
}

hal_entry.c 文件中添加 qspi.h 头文件,添加测试代码,写入一组字符串,一组 float 数据,一组 int 数据,分别读出来打印到串口查看读写数据。

#define Flash_Opt_Addr 0x00000000

uint8_t txString[] = "either write something worth reading or do something worth writing.";
uint8_t rxString[sizeof(txString)];

double txFloat[10] = {1.2, 2.54, 3.47, 4.12, 5.22, 6.31, 7.15, 8.65, 9.71, 10.94};
double rxFloat[10] = {0};

int txInt[10] = {11, 22, 33, 44, 55, 66, 77, 88, 99, 100};
int rxInt[10] = {0};

void hal_entry(void)
{
/* TODO: add your own code here */

led_Init();
uart0_Init();
QSPI_Flash_Init();

uint32_t Id = QSPI_Flash_ReadID();
uint32_t devId = QSPI_Flash_ReadDeviceID();

if(Id != 0 && devId != 0)
{
printf("FlashID: 0x%X, DeviceID: 0x%X \r\n", Id, devId);

//擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除
//擦除4K,即一个扇区,擦除的最小单位是扇区
QSPI_Flash_SectorErase(Flash_Opt_Addr);

//写一页,一页的大小为256个字节
QSPI_Flash_BufferWrite(txString, Flash_Opt_Addr, sizeof(txString));
printf("write string: %s \r\n", txString);

//将写入的数据读出来
QSPI_Flash_BufferRead(rxString, Flash_Opt_Addr, sizeof(txString));
printf("read string: %s \r\n", rxString);

R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //延时1秒

//前面已擦除整个扇区和写入第0页,现继续写入第1页和第2页
//写入float数据到第一页
QSPI_Flash_BufferWrite((void *)txFloat, SPI_FLASH_PageSize * 1, sizeof(txFloat));
printf("write float: ");
for(int i=0; i<10; i++)
printf("%f, ", txFloat[i]);
printf("\r\n");

//读取float数据
QSPI_Flash_BufferRead((void *)rxFloat, SPI_FLASH_PageSize * 1, sizeof(txFloat));
printf("read float: ");
for(int i=0; i<10; i++)
printf("%f, ", rxFloat[i]);
printf("\r\n");

R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //延时1秒

//写入第二页
QSPI_Flash_BufferWrite((void *)txInt, SPI_FLASH_PageSize * 2, sizeof(txInt));
printf("write int: ");
for(int i=0; i<10; i++)
printf("%d, ", txInt[i]);
printf("\r\n");

//读取整数数据
QSPI_Flash_BufferRead((void *)rxInt, SPI_FLASH_PageSize * 2, sizeof(txInt));
printf("read int: ");
for(int i=0; i<10; i++)
printf("%d, ", rxInt[i]);
printf("\r\n");
}


while(1)
{

LED1_TOGGLE();
R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS); //延时1秒

}


#if BSP_TZ_SECURE_BUILD
/* Enter non-secure code */
R_BSP_NonSecureEnter();
#endif
}

下载验证

连接下载器,将程序编译并下载到开发板之后,按下复位按键来复位开发板,程序启动初始化完。

可以看到打印读写的数据。